from typing import List, Tuple, Optional, Type
import json
import base64
import secrets
from fastapi import APIRouter, HTTPException, status, Depends, WebSocket, WebSocketDisconnect, Request
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from ..models import Info, infoIn_pydantic, infoOut_pydantic

from tortoise.signals import post_save, post_delete


router = APIRouter(
    prefix="/viminfo",
    tags=["info"],
)

security = HTTPBasic()


def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
    correct_username = secrets.compare_digest(credentials.username, "seadog")
    correct_password = secrets.compare_digest(credentials.password, "vimatremote")
    if not (correct_username and correct_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect email or password",
            headers={"WWW-Authenticate": "Basic"},
        )
    return credentials.username


@router.get("/allviminfo")
async def get_all_viminfo():
    return await infoOut_pydantic.from_queryset(Info.all())


@router.get("/")
async def get_all_product(username: str = Depends(get_current_username)):
    return await infoOut_pydantic.from_queryset(Info.all())


@router.post("/createnewviminfofrombrower")
async def create_info(info: infoIn_pydantic):
    datas = info.dict()
    datas["body"] = base64.b64encode(datas["body"].encode()).decode()
    new_obj = await Info.create(**datas)
    return "success"


@router.post("/")
async def create_info(info: infoIn_pydantic, username: str = Depends(get_current_username)):
    datas = info.dict()
    new_obj = await Info.create(**datas)
    # return {"create": "success"}
    return "success"


async def get_id(code: int) -> int:
    id_list = await Info.all().limit(1000 - code).values_list("id")
    pk = id_list[-1][0] if len(id_list) != 0 else 0  # deal with when no data inside DB
    return pk


@router.get("/{code}")
async def info_detail(code: int, username: str = Depends(get_current_username)):
    obj = await Info.get_or_none(id=await get_id(code))
    if not obj:
        return base64.b64encode(b"no data in DB!")
    data = await infoOut_pydantic.from_tortoise_orm(obj)
    data = data.dict()
    return data["body"]


@router.delete("/deleteall")
async def productIn_delete(username: str = Depends(get_current_username)):
    await Info.all().delete()
    return "all deleted"


@router.delete("/{id}")
async def viminfo_delete(id: int):
    obj = await Info.get(id=id)
    data = await infoOut_pydantic.from_tortoise_orm(obj)
    await obj.delete()
    return {"delete": id}


class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []
        self.clients_id: List[WebSocket] = []

    async def connect(self, websocket: WebSocket, client_id: str):
        await websocket.accept()
        self.active_connections.append(websocket)
        self.clients_id.append(client_id)

    def disconnect(self, websocket: WebSocket):
        client_id = self.active_connections.index(websocket)
        self.active_connections.remove(websocket)
        self.clients_id.pop(client_id)

    async def send_personal_message(self, message: str, websocket: WebSocket):
        await websocket.send_text(message)

    # @done 广播时排除自己
    async def broadcast_exclude_talker(self, message: str, current_talker: WebSocket):
        for connection in self.active_connections:
            if not connection is current_talker:
                await connection.send_text(message)

    async def broadcast_all(self, message: str):
        for connection in self.active_connections:
            # await connection.send_text(message)
            await connection.send_json(message)


manager = ConnectionManager()
global_websocket = []


@router.websocket("/ws/update/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
    await manager.connect(websocket, client_id)
    try:
        while True:
            # @done client should info server,operation type: del,add. server will react accordingly and return proper data
            # @mark 由于通过signal出发 websocket给client发送数据，这里的数据接受没有意义了
            data = await websocket.receive_text()
    except WebSocketDisconnect:
        manager.disconnect(websocket)


# @todo using enum to limit operation to : add / del / clients-update
async def process_instance(operation: str, instance: Info):
    if operation in ("add", "del"):
        note_obj_out = await infoOut_pydantic.from_tortoise_orm(instance)
        data = {"operation": operation}
        data.update(json.loads(note_obj_out.json()))  # note_obj_out.json() 返回结果为str!!!
        return data


@post_save(Info)
async def after_create_new_info(sender: "Type[Info]", instance: Info, created: bool, using_db: "Optional[BaseDBAsyncClient]", update_fields: List[str]) -> None:
    if created:
        data = await process_instance("add", instance)
        if data:
            await manager.broadcast_all(data)


@post_delete(Info)
async def after_info_delete(sender: "Type[Info]", instance: Info, using_db: "Optional[BaseDBAsyncClient]") -> None:
    data = await process_instance("del", instance)
    if data:
        await manager.broadcast_all(data)
